﻿using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading;

namespace MTP.Buffer
{
    class AsyncBufferStream : Stream
    {

        private int endIndex = short.MaxValue;

        private readonly ConcurrentDictionary<int, byte[]> byteArray = new ConcurrentDictionary<int, byte[]>();

        public override bool CanRead => true;
        public override bool CanSeek => false;
        public override bool CanWrite => false;
        public override long Length { get; }
        public override long Position { get; set; }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="index"></param>
        /// <param name="bytes"></param>
        /// <param name="isEnd"></param>
        public void Append(int index, byte[] bytes, bool isEnd)
        {
            if (isEnd)
                endIndex = index;
            byteArray.TryAdd(index, bytes);
        }

        public override void Flush()
        {

        }

        private byte[] GetBytes(ref long position)
        {
            for (int i = 0; i < short.MaxValue; i++)
            {
                SpinWait.SpinUntil(() => i > endIndex || byteArray.ContainsKey(i));
                if (!byteArray.TryGetValue(i, out var bs))
                {
                    return Array.Empty<byte>();
                }
                if (position >= bs.Length)
                {
                    position -= bs.Length;
                }
                else
                {
                    return bs;
                }
            }
            throw new IndexOutOfRangeException("offset");
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            int retLength = 0;
            while (count > 0)
            {
                long position = Position;
                var bytes = GetBytes(ref position);
                if (bytes.Length == 0)
                {
                    break;
                }
                int length = Math.Min(bytes.Length - (int)position, count);
                Array.Copy(bytes, position, buffer, offset, length);
                retLength += length;
                Position += length;
                offset += length;
                count -= length;
            }
            return retLength;
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            lock (this)
            {
                switch (origin)
                {
                    case SeekOrigin.Begin:
                        Position = offset;
                        break;
                    case SeekOrigin.Current:
                        Position += offset;
                        break;
                    case SeekOrigin.End:
                        break;
                    default:
                        break;
                }
                return Position;
            }
        }

        public override void SetLength(long value)
        {
            throw new NotImplementedException();
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            throw new NotImplementedException();
        }

        public override void Close()
        {
            base.Close();
        }
    }
}
